﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;

namespace ApplicationSettingsManager
{
	public static class DynamicObjectMethods
	{

		/// <summary>
		/// Gets the dynamic property value.
		/// This method looks for the property by name in the dynamic object 
		/// and retrieves the value of that property if found
		/// </summary>
		/// <param name="propertyName">Name of the property.</param>
		/// <param name="dynObject">The dyn object.</param>
		/// <returns></returns>
		public static dynamic GetObjectPropertyValue(string propertyName, dynamic dynObject)
		{
			PropertyInfo property = dynObject.GetType().GetProperty(propertyName);

			return property == null ? null : property.GetValue(dynObject, null);
		}

		/// <summary>
		/// Calls the method.
		/// </summary>
		/// <param name="objectType">Type of the object that contains the method.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <param name="sourceRow">The source row to search for parameters in.</param>
		/// <returns></returns>
		public static dynamic CallMethod(Type objectType, string methodName, DataRow sourceRow)
		{
			var methodInfo = objectType.GetMethod(methodName);
			var parameters = new List<object>();

			methodInfo.GetParameters();

			foreach (var param in methodInfo.GetParameters())
			{
				var name = param.Name;
				object value = null;

				if (sourceRow.Table.Columns.Contains(name))
					value = GetValueFromColumn(sourceRow[name]);
				;
				parameters.Add(value);
			}

			return methodInfo.Invoke(null, parameters.ToArray());
		}

		/// <summary>
		/// Calls the method.
		/// </summary>
		/// <param name="objectType">Type of the object that contains the method.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <param name="sourceParams"></param>
		/// <returns></returns>
		public static dynamic CallMethod(Type objectType, string methodName, dynamic sourceParams)
		{
			var methodInfo = objectType.GetMethod(methodName);
			var parameters = new List<object>();

			methodInfo.GetParameters();

			foreach (var param in methodInfo.GetParameters())
			{
				var value = DynamicObjectMethods.GetObjectPropertyValue(param.Name, sourceParams);

				parameters.Add(value);
			}

			return methodInfo.Invoke(null, parameters.ToArray());
		}

		/// <summary>
		/// Sets the list object values for type T; List of T or Array of T[].
		/// </summary>
		/// <param name="trgtPropInfo">The Target property info.</param>
		/// <param name="sourceList">The source list or array of type T.</param>
		/// <returns></returns>
		public static dynamic SetListObjectValues(PropertyInfo trgtPropInfo, dynamic sourceList, int mType)
		{
			dynamic targetList;             // empty list object of type T
			dynamic targetListObjectType;   // type T of list object property
			dynamic target;                 // object of type T

			// get the property type of the list object type T; List<T> / T[]
			dynamic targetListPropertyType = trgtPropInfo.PropertyType;

			// targetList is an Array T[]. 
			if (targetListPropertyType.IsArray)
			{
				//get the target property type that is the array object type T
				targetListObjectType = targetListPropertyType.GetElementType();

				// instantiate an empty array list of the object type
				targetList = Array.CreateInstance(targetListObjectType, sourceList.Count);

				var i = 0;
				foreach (var source in sourceList)
				{
					// instantiate the object that is the list object type T
					target = Activator.CreateInstance(targetListObjectType);

					//if unable to instantiate the list object, skip
					if (target == null) continue;

					dynamic addObject = mType == 1 ? SetServiceTargetPropertyValues(target, source) : GetServiceSourcePropertyValues(target, source);

					//array[i] = item as typeof(type);
					targetList[i] = addObject;
					i++;
				}
			}
			else // targetList is a List<T>.
			{
				// instantiate an empty List<T> of the object type
				targetList = Activator.CreateInstance(trgtPropInfo.PropertyType);

				//get the target property type that is the List<T> object type T
				targetListObjectType = targetList.GetType().GetProperty("Item").PropertyType;

				foreach (var source in sourceList)
				{
					// instantiate the object that is the list object type T
					target = Activator.CreateInstance(targetListObjectType);

					//if unable to instantiate the list object, skip
					if (target == null) continue;

					dynamic addObject = mType == 1 ? SetServiceTargetPropertyValues(target, source) : GetServiceSourcePropertyValues(target, source);

					targetList.Add(addObject);
				}
			}

			return targetList;
		}

		/// <summary>
		/// Sets the target property values.
		/// This method loops through the source properties and gets
		/// the equivalent target property.
		/// If the property is found in the target object,
		/// that properties value is set in the target objects property.
		/// </summary>
		/// <param name="target">The Service (empty) target object.</param>
		/// <param name="source">The Local (data filled) source object.</param>
		/// <returns>
		/// The property value (data) filled target object
		/// </returns>
		public static dynamic SetServiceTargetPropertyValues(dynamic target, dynamic source)
		{
			//loop through all source object properties declared
			foreach (var srcProperty in source.GetType().GetProperties())
			{
				// get the source object property value
				var propertyValue = srcProperty.GetValue(source, null);

				// Check of the source property value is null.
				// This enables nullable (OPTIONAL) fields 
				// to be ignored if no value is present at runtime
				if (propertyValue == null) continue;

				// Get the equivalent target object property
				var trgtProperty = target.GetType().GetProperty(srcProperty.Name);

				// Check of the target property is null (does not exist).
				// This is a guarantee if the target property sdoes not exist
				// we do not attempt to pass a value to it.
				if (trgtProperty == null) continue;

				// Check if the current target property and the source property are user defined objects with properties
				// if so, use recursion to fill all levels of objects in the target object
				if (IsUserDefinedObject(trgtProperty.PropertyType, srcProperty.PropertyType))
				{
					if (trgtProperty.PropertyType.IsArray)
					{
						//get the target property type that is the list object
						var tpType = trgtProperty.PropertyType.GetElementType();

						// did we get the target property type?
						if (tpType == null) continue;

						trgtProperty.SetValue(target, SetListObjectValues(trgtProperty, propertyValue, 1), null);
					}
					else if ((srcProperty.PropertyType.IsEnum) && (trgtProperty.PropertyType == typeof(string)))
					{   //the source is an enum and the target is not
						// recursion is not necessary, get string value of enum
						trgtProperty.SetValue(target, propertyValue.ToString(), null);
					}
					else if ((srcProperty.PropertyType == typeof(string)) && (trgtProperty.PropertyType.IsEnum))
					{   //the source is a string value and the target is an enum type
						// recursion is not necessary, set the value of enum with string value
						trgtProperty.SetValue(target, Enum.Parse(trgtProperty.PropertyType, propertyValue), null);
					}
					else
					{
						// instantiate the object that is the property type
						var nestObject = trgtProperty.PropertyType.InvokeMember(trgtProperty.Name, BindingFlags.CreateInstance, null, null, null);

						// did we get the nested class object?
						if (nestObject == null) continue;

						// recurse through the new objects
						trgtProperty.SetValue(target, SetServiceTargetPropertyValues(nestObject, propertyValue), null);
					}

				}
				else //set the System Property value in the Web Service Object 
					trgtProperty.SetValue(target, propertyValue, null);

			}

			return target;
		}

		/// <summary>
		/// Gets the source property values.
		/// This method loops through the target object's properties and gets the
		/// equivalent source property.
		/// If the property is found in the source object,
		/// that properties value is set in the target objects property.
		/// </summary>
		/// <param name="target">The (empty) target object.</param>
		/// <param name="source">The data filled web service source object.</param>
		/// <returns>
		/// The property value (data) filled target object
		/// </returns>
		public static dynamic GetServiceCamelCaseSourcePropertyValues(dynamic target, dynamic source)
		{
			//loop through all properties declared in the dynamic target object
			foreach (var trgtProperty in target.GetType().GetProperties())
			{
				string propertyName = trgtProperty.Name;
				bool found = false;
				// find the equivalent source object property
				var srcProperty = source.GetType().GetProperty(MakeCamelCase(propertyName));
				if (srcProperty != null)
					found = true;

				if (!found)
					srcProperty = source.GetType().GetProperty(trgtProperty.Name);

				if (srcProperty != null)
					found = true;

				if (!found)
					srcProperty = source.GetType().GetProperty(propertyName.ToLower());

				// Check of the property is null (does not exist).
				if (srcProperty == null) continue;

				// get the source object property value
				var propertyValue = srcProperty.GetValue(source, null);

				// Check of the property value is null.
				if (propertyValue == null) continue;

				// Check if the current target property and the source property are user defined objects with properties
				// if so, use recursion to fill all levels of objects in the target object
				if (IsUserDefinedObject(srcProperty.PropertyType, trgtProperty.PropertyType))
				{
					if (srcProperty.PropertyType.IsArray)
					{
						trgtProperty.SetValue(target, SetListObjectValues(trgtProperty, propertyValue, 2), null);
					}
					else if ((srcProperty.PropertyType.IsEnum) && (trgtProperty.PropertyType == typeof(string)))
					{   //the source is an enum and the target is not
						// recursion is not necessary, get string value of enum
						trgtProperty.SetValue(target, propertyValue.ToString(), null);
					}
					//else if ((srcProperty.PropertyType.IsEnum) && (trgtProperty.PropertyType.IsEnum))
					//{   //both properties are enums
					//    // recurse through the enum objects
					//    trgtProperty.SetValue(target, propertyValue, null);
					//}
					else
					{
						// instantiate the object that is the property type
						var nestObject = trgtProperty.PropertyType.InvokeMember(trgtProperty.Name, BindingFlags.CreateInstance, null, null, null);

						// did we get the nested class object?
						if (nestObject == null) continue;

						// recurse through the new objects
						trgtProperty.SetValue(target, GetServiceSourcePropertyValues(nestObject, propertyValue), null);
					}

				}
				else //set the System Property value in the Web Service Object 
					trgtProperty.SetValue(target, propertyValue, null);

			}

			return target;
		}

		/// <summary>
		/// Gets the service source property values.
		/// This method loops through the target object's properties and gets the
		/// equivalent source property.
		/// If the property is found in the source object,
		/// that properties value is set in the target objects property.
		/// </summary>
		/// <param name="target">The (empty) target object.</param>
		/// <param name="source">The data filled web service source object.</param>
		/// The property value (data) filled target object
		/// <returns></returns>
		public static dynamic GetServiceSourcePropertyValues(dynamic target, dynamic source)
		{
			//loop through all properties declared in the dynamic target object
			foreach (var trgtProperty in target.GetType().GetProperties())
			{
				// find the equivalent source object property
				var srcProperty = source.GetType().GetProperty(trgtProperty.Name);

				// Check of the property is null (does not exist).
				// This makes a guarantee if property does not exist
				// we do not attempt to get a value from it.
				if (srcProperty == null) continue;

				// get the source object property value
				var propertyValue = srcProperty.GetValue(source, null);

				// Check of the property value is null.
				// This makes a guarantee if property value does not exist
				// we do not attempt to get a value from it.
				if (propertyValue == null) continue;

				// Check if the current target property and the source property are user defined objects with properties
				// if so, use recursion to fill all levels of objects in the target object
				if (IsUserDefinedObject(srcProperty.PropertyType, trgtProperty.PropertyType))
				{
					if (srcProperty.PropertyType.IsArray)
					{
						trgtProperty.SetValue(target, SetListObjectValues(trgtProperty, propertyValue, 2), null);
					}
					else if ((srcProperty.PropertyType.IsEnum) && (trgtProperty.PropertyType == typeof(string)))
					{   //the source is an enum and the target is not
						// recursion is not necessary, get string value of enum
						trgtProperty.SetValue(target, propertyValue.ToString(), null);
					}
					//else if ((srcProperty.PropertyType.IsEnum) && (trgtProperty.PropertyType.IsEnum))
					//{   //both properties are enums
					//    // recurse through the enum objects
					//    trgtProperty.SetValue(target, propertyValue, null);
					//}
					else
					{
						// instantiate the object that is the property type
						var nestObject = trgtProperty.PropertyType.InvokeMember(trgtProperty.Name, BindingFlags.CreateInstance, null, null, null);

						// did we get the nested class object?
						if (nestObject == null) continue;

						// recurse through the new objects
						trgtProperty.SetValue(target, GetServiceSourcePropertyValues(nestObject, propertyValue), null);
					}

				}
				else //set the System Property value in the Web Service Object 
					trgtProperty.SetValue(target, propertyValue, null);

			}

			return target;
		}

		/// <summary>
		/// Determines if the properties are class object types and need recursion.
		/// If this method is failing, 
		/// ensure the FBMC user defined objects are created properly
		/// </summary>
		/// <param name="trgtPropType">Type of the target property.</param>
		/// <param name="srcPropType">Type of the source property.</param>
		/// <returns></returns>
		public static bool IsUserDefinedObject(Type trgtPropType, Type srcPropType)
		{
			// the namespace of the defined objects
			var trgNamespace = trgtPropType.Namespace;
			var srcNamespace = srcPropType.Namespace;

			// both Objects are System defined objects
			if ((trgNamespace == "System") && (srcNamespace == "System")) return false;

			// both Objects are "User" defined (i.e. not "System" defined objects)
			if ((!trgNamespace.Contains("System.")) && (!srcNamespace.Contains("System.")))
				return true;

			// see if either object is a "User" defined object 
			if ((!trgNamespace.Contains("System.")) || (!srcNamespace.Contains("System.")))
			{
				/***** at least one object is user defined *****/

				// see if either object is an enumeration
				if ((trgtPropType.IsEnum) || (srcPropType.IsEnum))
					return true;

				// see if both objects are an array or a list
				if (((trgtPropType.IsArray) || (trgtPropType.IsSerializable))
					&& ((srcPropType.IsArray) || (srcPropType.IsSerializable)))
					return true;
			}

			// both objects are system defined objects and not enums
			// no recursion is necessary
			return false;
		}

		/// <summary>
		/// Gets the value from column.
		/// </summary>
		/// <param name="value">The value.</param>
		/// <returns></returns>
		public static dynamic GetValueFromColumn(object value)
		{
			if (Convert.IsDBNull(value)) return null;

			var rtnValue = default(dynamic);

			if (value is string)
				rtnValue = Convert.ToString(value);
			else if (value is int)
				rtnValue = Convert.ToInt32(value);
			else if (value is DateTime)
				rtnValue = Convert.ToDateTime(value);
			else if (value is decimal)
				rtnValue = Convert.ToDecimal(value);

			return rtnValue;
		}

		private static string MakePascalCase(string inStr)
		{
			if (inStr.Length > 0)
			{
				if (inStr.ToUpper() == "ID")	// Always return an ID as a capped field
					return inStr.ToUpper();

				//convert the first letter in the word to uppercase
				char firstLetter = char.ToUpper(inStr[0]);

				//concantenate the uppercase letter to the rest of the word
				inStr = firstLetter + inStr.Substring(1);
			}
			return inStr;
		}

		private static string MakeCamelCase(string inStr)
		{
			if (inStr.Length > 0)
			{
				//convert the first letter in the word to uppercase
				char firstLetter = char.ToLower(inStr[0]);

				//concantenate the uppercase letter to the rest of the word
				inStr = firstLetter + inStr.Substring(1);
			}
			return inStr;
		}



	}
}
